
import logging
import etcd3
import time
import sys
import json
from flask import request
from flask import current_app

import config

class BaseSafe:
    ''' 敏感字符检测与处理 '''

    def __init__(self,req:request) -> None:
        self.request = req

    def XSS(self): 
        ''' XSS敏感字符检测 

            Returns: bool, str

        '''
        # url参数检测
        for i in self.request.args: 
            b, s = self.CheckDangerousStr(self.request.args[i])
            if b == False:  
                return b, "request.args, keys = {0}, dangerous text: {1}".format(
                    str(i), str(self.request.args[i])
                )
        # 表单参数检测
        for i in self.request.form: 
            b, s = self.CheckDangerousStr(i) 
            if b == False:
                return b, "request.form, the keyname contains invalid characters."
            b, s = self.CheckDangerousStr(self.request.form[i])
            if b == False: 
                return b, "request.form, the value contains invalid characters."
        # 请求头信息检测
        for i in self.request.headers:
            l = list(i)
            b, s = self.CheckDangerousStr(l[1])
            if b == False: 
                return b, "request.headers, the value contains invalid characters. "
        # 请求的json数据检测
        if self.request.is_json:
            kvl = self.JsonEach(self.request.get_json())
            ct = ''
            for i in kvl: ct = ct + str(i)
            b, s = self.CheckDangerousStr(ct)
            if b == False: 
                return b, "request.data, the json contains invalid characters."
        return True, ""


    def CheckDangerousStr(self,s:str):
        ''' 危险字符检测，返回结果和位置

            Returns: 
                bool, index
        '''
        if s.find("<") > -1: return False, s.find("<")
        if s.find(">") > -1: return False, s.find(">")
        if s.find("&") > -1: return False, s.find("&")
        return True, -1

    def JsonEach(self,j:dict):
        ''' 遍历所有的键值

            Returns:[k,v]
        '''
        re = []
        for i in j:
            if type(j[i]) == str:
                re.append(i)
                re.append(j[i])
            if type(j[i]) == dict:
                re.append(i)
                for ii in self.JsonEach(j[i]):
                    re.append(ii)
        return re


class ApiCount: 
    ''' 接口统计 '''
    def __init__(self, req: request) -> None:
        pass


class AppConfig: 
    ''' 配置中心请求 '''
    def __init__(self) -> None:
        pass

    def BeforeRequest(self): 
        ''' 在访问 flask 接口时，触发配置信息处理。 '''
        _conf = {}
        #
        if config.CONFIG_MOD == 'etcd':
            #过期判断
            try: 
                with current_app.app_context(): 
                    if time.time() - current_app.config['CONF']['_time'] <= config.ETCD_CACHE_MAXTIME: 
                        return True
            except:
                pass

            #获取ETCD的配置
            etcddata = self.GetEtcdData()
            logging.info("etcddata, " + json.dumps(etcddata, indent=2, sort_keys=True))
            #融合本地配置
            localconfigkv = self.GetConfigKV()
            for i in localconfigkv: 
                _conf[str(i)] = localconfigkv[str(i)]
            #融合etcd配置
            for i in etcddata: 
                if i in config.ETCD_MAP: 
                    _conf[config.ETCD_MAP[str(i)]] = etcddata[i]
                else:
                    #ETCD存在的键而本地配置不存在的键
                    _conf[i] = etcddata[i]
            if etcddata != []: 
                with current_app.app_context(): 
                    current_app.config.update({"CONF": _conf})
            else:
                logging.warn("etcd 数据不可用，不更新配置信息。")
        else:
            logging.info("Config Mod: local")

        print('_conf\n',json.dumps(_conf, indent=2, sort_keys=True))
        pass

    def GetEtcdData(self):
        ''' 从etcd服务器拉取数据 '''
        _conf = self.GetConfigKV()
        for i in _conf['ETCD_HOST']:
            try:
                etcd = etcd3.client(
                    host= i.rsplit(":")[0],
                    port= i.rsplit(":")[1],
                    user= _conf['ETCD_USER'],
                    password= _conf['ETCD_PWD'],
                    timeout=10
                )
                re = {"_time": time.time()}
                for data, meta in etcd.get_prefix(_conf['ETCD_ROOT_DIR']): 
                    _value = data.decode()
                    _key = str(meta.key.decode()).replace(_conf['ETCD_ROOT_DIR'],"")
                    re[_key] = _value
                etcd.close()
                return re
            except Exception as e:
                logging.error("Failed To Connect Etcd: {0} ".format(str(i)) )
        return []

    def GetConfigKV(self):
        ''' 获取配置文件的配置项 

            当应用启动参数带 --debug 时，将使用debug的配置。

            返回Json化的config.py 配置。
        '''
        _n = [
            '__name__',
            '__doc__',
            '__package__',
            '__loader__',
            '__spec__',
            '__file__',
            '__cached__',
            '__builtins__',
            '__dict__',
            '__weakref__',
            '__module__',
            'DEBUG'
        ]
        redata = {}
        confdict = config.__dict__
        for i in confdict: 
            if str(i) in _n: continue
            redata[str(i)] = confdict[str(i)]
        # debug 更改
        if "--debug" in sys.argv:
            debugdict = config.DEBUG.__dict__
            for i in debugdict: 
                if str(i) in _n: continue
                redata[str(i)] = debugdict[str(i)]
        #
        return redata
        
